#include <iostream>
#include <string>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <strings.h>
#include <unistd.h>
#include <pthread.h>

#define SIZE 1024

void Use(const std::string proc)
{
    std::cout << "\n\t" << proc << " serveip serveport\n" << std::endl;
}

struct ThreadDate
{
    struct sockaddr_in server;
    int sockfd;
};

void* recv_message(void* argv)
{
    struct ThreadDate* td = static_cast<ThreadDate*>(argv);
    char buffer[SIZE];
    while(true)
    {
        memset(buffer, 0, sizeof(buffer));
        // recvfrom接收数据
        
        // 在接收消息的时候，可能会从多台主机上收消息，所以recvfrom后面的参数就不能是上述确定的某一个服务器
        // 但是又必须要填参数，所以这里新创建一个sockaddr_in
        struct sockaddr_in temp;
        socklen_t len = sizeof(temp);
        
        ssize_t s = recvfrom(td->sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&temp,&len);
        // std::cout << "recvfrom over"<<std::endl;
        if(s > 0)
        {
            buffer[s] = 0;
            std::cerr<<buffer<<std::endl;
        }
    }
}

void* send_message(void* argv)
{
    ThreadDate* td = static_cast<ThreadDate*>(argv);
    std::string message;
    socklen_t len = sizeof(td->server);
    while(true)
    {
        // 从cin中获得数据
        std::cout<<"Please enter#";
        getline(std::cin,message);

        // std::cout<<message<<std::endl;
        // sendto发送数据
        int st = sendto(td->sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&td->server,len);
        if(st<0)
        {
            std::cout<<"sendto error"<<std::endl;
            continue;
        }

    }
}
// 这个是多线程版本的，思路是创建两个线程，一个线程从服务端中读数据，一个线程向服务端中发送数据
int main(int argc,char* argv[])
{
    // 检查命令行
    if(argc != 3)
    {
        Use(argv[0]);
        exit(1);
    }
    // .udpclient serveip serveport
    std::string serveip = argv[1];
    uint16_t serveport = std::stoi(argv[2]);

    // 初始化server sockaddr_in，为了在后面sendto给server
    ThreadDate td;
    bzero(&td.server,sizeof(td.server)); 

    td.server.sin_family = AF_INET;
    td.server.sin_port = htons(serveport); // 需要从主机序列转换成网络序列
    td.server.sin_addr.s_addr = inet_addr(serveip.c_str()); // 把点分十进制格式的IPv4地址转换为网络字节序的 32 位无符号整数


    // sockfd，类似与创建文件描述符，在最后记得关闭，其实不关闭也行，毕竟最后程序都结束了，sockfd的声明周期是随进程的
    td.sockfd = socket(AF_INET,SOCK_DGRAM,0);

    if(td.sockfd < 0)
    {
        std::cout<<"socket error"<<std::endl;
        exit(2);
    }

    // 创建两个线程
    pthread_t recvr,sender;
    pthread_create(&recvr,nullptr,recv_message,&td);
    pthread_create(&sender,nullptr,send_message,&td);

    pthread_join(recvr,nullptr);
    pthread_join(sender,nullptr);

    close(td.sockfd);
    return 0;
}

// // 如下是单进程版本的
// int main(int argc,char* argv[])
// {
//     // 检查命令行
//     if(argc != 3)
//     {
//         Use(argv[0]);
//         exit(1);
//     }
//     // .udpclient serveip serveport
//     std::string serveip = argv[1];
//     uint16_t serveport = std::stoi(argv[2]);

//     // 初始化server sockaddr_in，为了在后面sendto给server
//     struct sockaddr_in server;
//     bzero(&server,sizeof(server)); 

//     server.sin_family = AF_INET;
//     server.sin_port = htons(serveport); // 需要从主机序列转换成网络序列
//     server.sin_addr.s_addr = inet_addr(serveip.c_str()); // 把点分十进制格式的IPv4地址转换为网络字节序的 32 位无符号整数
//     socklen_t len = sizeof(server);

//     // sockfd，类似与创建文件描述符，在最后记得关闭，其实不关闭也行，毕竟最后程序都结束了，sockfd的声明周期是随进程的
//     int sockfd = socket(AF_INET,SOCK_DGRAM,0);

//     if(sockfd < 0)
//     {
//         std::cout<<"socket error"<<std::endl;
//         exit(2);
//     }

//     // client客户端要bind吗？要！只不过不需要用户显示的bind！一般有OS自由随机选择
//     // 一个端口号只能被一个进程bind，对server是如此，对于client，也是如此
//     // 其实client的port是多少，其实不重要，只要能保证主机上的唯一性就可以
//     // 系统什么时候给我bind呢？首次发送数据的时候

//     std::string message;
//     char buffer[SIZE];
//     while(true)
//     {
//         // 从cin中获得数据
//         std::cout<<"Please enter#";
//         getline(std::cin,message);

//         // std::cout<<message<<std::endl;
//         // sendto发送数据
//         int st = sendto(sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&server,len);
//         if(st<0)
//         {
//             std::cout<<"sendto error"<<std::endl;
//             continue;
//         }
        
//         // recvfrom接收数据

//         // 在接收消息的时候，可能会从多台主机上收消息，所以recvfrom后面的参数就不能是上述确定的某一个服务器
//         // 但是又必须要填参数，所以这里新创建一个sockaddr_in
//         struct sockaddr_in temp;
//         socklen_t len = sizeof(temp);

//         ssize_t s = recvfrom(sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&temp,&len);
//         // std::cout << "recvfrom over"<<std::endl;
//         if(s > 0)
//         {
//             buffer[s] = 0;
//             std::cout<<buffer<<std::endl;
//         }
//     }
//     close(sockfd);
//     return 0;
// }